﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Text;

namespace PowerGuiVsx.Module
{
    internal class ProcessUtils
    {
        public static void CopyFile(string fileName, string targetFile, string computerName, string username, string password)
        {
            IntPtr admin_token = IntPtr.Zero;
            try
            {
                if (LogonUser(username, computerName, password, (int)LogonType.NewCredentials, (int)LogonProvider.Default, ref admin_token) != 0)
                {
                    using (WindowsImpersonationContext impersonationContext = new WindowsIdentity(admin_token).Impersonate())
                    {
                        File.Copy(fileName, targetFile, true);
                        impersonationContext.Undo();
                    }
                }
                else
                {
                    throw new Exception("Failed to authenticate");
                }
            }
            finally
            {
                CloseHandle(admin_token);
            }
        }

        public static void CopyFolder(string folderName, string targetFolder, string computerName, string username, string password)
        {
            IntPtr admin_token = IntPtr.Zero;
            try
            {
                if (LogonUser(username, computerName, password, (int)LogonType.NewCredentials, (int)LogonProvider.Default, ref admin_token) != 0)
                {
                    using (WindowsImpersonationContext impersonationContext = new WindowsIdentity(admin_token).Impersonate())
                    {
                        DirectoryCopy(folderName, targetFolder, true);
                        impersonationContext.Undo();
                    }
                }
                else
                {
                    throw new Exception("Failed to authenticate");
                }
            }
            finally
            {
                CloseHandle(admin_token);
            }
        }

        public static uint StartRemoteProcess(string fileName, string computerName, string username, SecureString password)
        {
            ConnectionOptions connOptions = new ConnectionOptions();
            connOptions.Impersonation = ImpersonationLevel.Impersonate;
            connOptions.EnablePrivileges = true;
            connOptions.Username = username;
            connOptions.SecurePassword = password;
            ManagementScope manScope = new ManagementScope(String.Format(@"\\{0}\ROOT\CIMV2", computerName), connOptions);
            manScope.Connect();
            ObjectGetOptions objectGetOptions = new ObjectGetOptions();
            ManagementPath managementPath = new ManagementPath("Win32_Process");
            
            ManagementClass processClass = new ManagementClass(manScope, managementPath, objectGetOptions);
            ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
            inParams["CommandLine"] = fileName;
            ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null);

            switch ((int)outParams["returnValue"])
            {
                case 0:
                    break;
                case 2:
                    throw new Exception("Access Denied");
                case 3:
                    throw new Exception("Insufficient Privilege");
                case 8:
                    throw new Exception("Unknown failure");
                case 9:
                    throw new Exception("Path Not Found");
                case 21:
                    throw new Exception("Invalid Parameter");
                default:
                    throw new Exception("Unspecified error");
            }

            return System.Convert.ToUInt32(outParams["processId"]);
        }

        private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
        {
            DirectoryInfo dir = new DirectoryInfo(sourceDirName);
            DirectoryInfo[] dirs = dir.GetDirectories();

            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException(
                    "Source directory does not exist or could not be found: "
                    + sourceDirName);
            }

            if (!Directory.Exists(destDirName))
            {
                Directory.CreateDirectory(destDirName);
            }

            FileInfo[] files = dir.GetFiles();
            foreach (FileInfo file in files)
            {
                string temppath = Path.Combine(destDirName, file.Name);
                file.CopyTo(temppath, false);
            }

            if (copySubDirs)
            {
                foreach (DirectoryInfo subdir in dirs)
                {
                    string temppath = Path.Combine(destDirName, subdir.Name);
                    DirectoryCopy(subdir.FullName, temppath, copySubDirs);
                }
            }
        }

        [DllImport("advapi32.DLL", SetLastError = true)]
        private static extern int LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll")]
        public static extern bool CloseHandle(IntPtr hObject);

        /// <summary>
        /// Duplicates the token.
        /// </summary>
        /// <param name="existingTokenHandle">The existing token
        /// handle.</param>
        /// <param name="securityImpersonationLevel">The security impersonation
        /// level.</param>
        /// <param name="duplicateTokenHandle">The duplicate token
        /// handle.</param>
        /// <returns>True if the function succeeds, false if the function fails.
        /// To get extended error information, call GetLastError.</returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool DuplicateToken(
            IntPtr existingTokenHandle,
            SecurityImpersonationLevel securityImpersonationLevel,
            out IntPtr duplicateTokenHandle);


        /// <summary>
        /// The type of logon operation to perform.
        /// </summary>
        internal enum SecurityImpersonationLevel : int
        {
            /// <summary>
            /// The server process cannot obtain identification information
            /// about the client, and it cannot impersonate the client.  It is
            /// defined with no value given, and thus, by ANSI C rules,
            /// defaults to a value of zero.
            /// </summary>
            Anonymous = 0,

            /// <summary>
            /// The server process can obtain information about the client,
            /// such as security identifiers and privileges, but it cannot
            /// impersonate the client.  This is useful for servers that export
            /// their own objects, for example, database products that export
            /// tables and views.  Using the retrieved client-security
            /// information, the server can make access-validation decisions
            /// without being able to use other services that are using the
            /// client's security context.
            /// </summary>
            Identification = 1,

            /// <summary>
            /// The server process can impersonate the client's security
            /// context on its local system.  The server cannot impersonate the
            /// client on remote systems.
            /// </summary>
            Impersonation = 2,

            /// <summary>
            /// The server process can impersonate the client's security
            /// context on remote systems.
            /// NOTE: Windows NT:  This impersonation level is not supported.
            /// </summary>
            Delegation = 3
        }

        /// <summary>
        /// The type of logon operation to perform.
        /// </summary>
        internal enum LogonType : int
        {
            /// <summary>
            /// This logon type is intended for users who will be interactively
            /// using the computer, such as a user being logged on by a
            /// terminal server, remote shell, or similar process.
            /// This logon type has the additional expense of caching logon
            /// information for disconnected operations; therefore, it is
            /// inappropriate for some client/server applications, such as a
            /// mail server.
            /// </summary>
            Interactive = 2,

            /// <summary>
            /// This logon type is intended for high performance servers to
            /// authenticate plaintext passwords.
            /// The LogonUser function does not cache credentials for this
            /// logon type.
            /// </summary>
            Network = 3,

            /// <summary>
            /// This logon type is intended for batch servers, where processes
            /// may be executing on behalf of a user without their direct
            /// intervention.  This type is also for higher performance servers
            /// that process many plaintext authentication attempts at a time,
            /// such as mail or Web servers.
            /// The LogonUser function does not cache credentials for this
            /// logon type.
            /// </summary>
            Batch = 4,

            /// <summary>
            /// Indicates a service-type logon.  The account provided must have
            /// the service privilege enabled.
            /// </summary>
            Service = 5,

            /// <summary>
            /// This logon type is for GINA DLLs that log on users who will be
            /// interactively using the computer.
            /// This logon type can generate a unique audit record that shows
            /// when the workstation was unlocked.
            /// </summary>
            Unlock = 7,

            /// <summary>
            /// This logon type preserves the name and password in the
            /// authentication package, which allows the server to make
            /// connections to other network servers while impersonating the
            /// client.  A server can accept plaintext credentials from a
            /// client, call LogonUser, verify that the user can access the
            /// system across the network, and still communicate with other
            /// servers.
            /// NOTE: Windows NT:  This value is not supported.
            /// </summary>
            NetworkCleartext = 8,

            /// <summary>
            /// This logon type allows the caller to clone its current token
            /// and specify new credentials for outbound connections.  The new
            /// logon session has the same local identifier but uses different
            /// credentials for other network connections.
            /// NOTE: This logon type is supported only by the
            /// LOGON32_PROVIDER_WINNT50 logon provider.
            /// NOTE: Windows NT:  This value is not supported.
            /// </summary>
            NewCredentials = 9
        }

        /// <summary>
        /// Specifies the logon provider.
        /// </summary>
        internal enum LogonProvider : int
        {
            /// <summary>
            /// Use the standard logon provider for the system.
            /// The default security provider is negotiate, unless you pass
            /// NULL for the domain name and the user name is not in UPN format.
            /// In this case, the default provider is NTLM.
            /// NOTE: Windows 2000/NT:   The default security provider is NTLM.
            /// </summary>
            Default = 0,

            /// <summary>
            /// Use this provider if you'll be authenticating against a Windows
            /// NT 3.51 domain controller (uses the NT 3.51 logon provider).
            /// </summary>
            WinNT35 = 1,

            /// <summary>
            /// Use the NTLM logon provider.
            /// </summary>
            WinNT40 = 2,

            /// <summary>
            /// Use the negotiate logon provider.
            /// </summary>
            WinNT50 = 3
        }

    }
}
